import * as fs from "fs";
import * as path from 'path';
import * as rimraf from 'rimraf';
import { createHash } from "crypto";

/**
 * 清理目录, 默认不清理当前的目录
 * @param dirPath 要清理的目录
 * @param clearSelf 是否把当前目录一起删掉
 */
export function clearDir(dirPath: string, clearSelf: boolean = false) {
    if (!fs.existsSync(dirPath)) {
        console.warn('要清理的目录不存在');
        return;
    }
    let s = fs.lstatSync(dirPath);
    if (s.isDirectory()) {
        if (clearSelf) {
            rimraf(dirPath, (err) => {
                if (err) {
                    console.warn('清理目录错误');
                } else {
                    console.log(`清理 ${dirPath} 成功`);
                }
            });
        } else {
            let files = fs.readdirSync(dirPath);
            for (const ifile of files) {
                let fileUrl = path.join(dirPath, ifile);
                clearDir(fileUrl, true);
            }
        }
    } else {
        rimraf(dirPath, (err) => {
            if (err) {
                console.warn('清理目录错误');
            } else {
                console.log(`清理 ${dirPath} 成功`);
            }
        });
    }
}

export function delDir(dirPath: string, clearSelf: boolean = true) {
    let files = [];
    if (fs.existsSync(dirPath)) {
        files = fs.readdirSync(dirPath);
        files.forEach((file, index) => {
            let curPath = dirPath + "/" + file;
            if (fs.statSync(curPath).isDirectory()) {
                delDir(curPath); //递归删除文件夹
            } else {
                fs.unlinkSync(curPath); //删除文件
                // console.log(curPath + "删除成功")
            }
        });
        fs.rmdirSync(dirPath);
    }
}

export function isIgnoreDir(from: string, ignores?: IIgnoreOption) {
    if (!ignores) {
        return false;
    }
    if (!ignores.dir) {
        return false;
    }
    for (const el of ignores.dir) {
        if (from.match(el)) {
            return true;
        }
    }
    return false;
}

export function isIgnoreFile(from: string, ignores?: IIgnoreOption) {
    if (!ignores) {
        return false;
    }
    if (!ignores.file) {
        return false;
    }
    for (const el of ignores.file) {
        console.log(from.search(new RegExp(el)));
        console.log(from, el, from.match(new RegExp(el)));
        if (from.match(new RegExp(el))) {
            return true;
        }
    }
    return false;
}

export function findAllFiles(dirPath: string, fileTypes?: string[], excludeHash?: any) {
    let files = [];
    function checkFile(filePath) {
        if (excludeHash && excludeHash[filePath]) return;
        let stat = fs.lstatSync(filePath);
        if (stat.isDirectory()) {
            let dirFiles = fs.readdirSync(filePath);
            for (let i = 0; i < dirFiles.length; i++) {
                checkFile(path.join(filePath, dirFiles[i]));
            }
        } else if (!fileTypes || fileTypes.indexOf(path.extname(filePath)) != -1) {
            files.push(filePath);
        }
    }
    checkFile(dirPath);
    return files;
}

export interface IIgnoreOption {
    dir?: string[];
    file?: string[];
}

/**
 * 递归创建目录
 * @param dirPath 需要创建目录的路径
 */
export function createDir(dirPath: string, callback?: () => void) {
    fs.exists(dirPath, (exi) => {
        if (exi) {
            callback.apply(null);
        } else {
            createDir(path.dirname(dirPath), () => {
                fs.mkdir(dirPath, callback);
            });
        }
    });
}

export function createDirSync(dirPath: string, callback?: () => void) {
    if (fs.existsSync(dirPath)) {
        if (!!callback) {
            callback();
        }
    } else {
        createDirSync(path.dirname(dirPath), () => {
            fs.mkdirSync(dirPath);
            if (!!callback) {
                callback();
            }
        });
    }
}

export function writeFile(filePath: string, content: string | Buffer, callback?: (err) => void) {
    let dirPath = path.dirname(filePath);
    createDirSync(dirPath, () => {
        fs.writeFile(filePath, content, callback);
    })
}

/**
 * 通过文件的路径或者内容, 得到hash之后的文件名
 * @param filePath 文件的真实路径
 * @param fileData 文件的内容
 */
export function hashFile(filePath: string, fileData?: Buffer) {
    let md5 = createHash('md5');
    if (!fileData) {
        fileData = fs.readFileSync(filePath);
    }
    md5.update(fileData);
    let md5Str = md5.digest('hex');
    let fnext = path.extname(filePath);
    let fnHead = path.basename(filePath, fnext);
    let fn = fnHead + '_' + md5Str.substr(0, 8) + fnext;
    let realFn = path.join(path.dirname(filePath), fn);
    return realFn;
}

export function createManifest(dirPath: string, callback: (info: any) => void, hashNum: number = 8) {
    let manifest = new Manifest(dirPath, hashNum);
    manifest.create(dirPath);
    if (callback !== null) {
        // callback.apply(null, manifest.getMf());
        callback(manifest.getMf());
    }
}

export function moveFile(sourceFile: string, destPath: string) {
    createDirSync(path.dirname(destPath));
    var readStream = fs.createReadStream(sourceFile);
    var writeStream = fs.createWriteStream(destPath);
    readStream.pipe(writeStream);
}

class Manifest {
    private mf: any;
    private root: string;
    private hashNum = 8;

    constructor(dirPath: string, hashNum: number = 8) {
        this.mf = {};
        this.root = dirPath;
        this.hashNum = hashNum;
    }

    public getMf() {
        return this.mf;
    }

    public create(dirPath: string) {
        if (fs.existsSync(dirPath)) {
            let s = fs.statSync(dirPath);
            if (s.isDirectory()) {
                let fnList = fs.readdirSync(dirPath);
                for (const fn of fnList) {
                    let ifurl = path.join(dirPath, fn);
                    this.create(ifurl);
                }
            } else {
                // console.log(path.relative(this.root, dirPath));
                let fExt = path.extname(dirPath);
                let relativeFn = path.relative(this.root, dirPath);
                let tempNum = this.hashNum > 0 ? this.hashNum + 1 : 0;
                let oldName = relativeFn.substr(0, (relativeFn.length - fExt.length - tempNum));
                let fkey = oldName.split(path.sep).join('/') + fExt;
                let fn = relativeFn.split(path.sep).join('/');
                this.mf[fkey] = fn;
            }

        } else {
            console.log(`${dirPath}不存在!`);
        }
    }
}

class CpDir {
    private fileNum = 0;
    private callback?: () => void;
    private ignores: IIgnoreOption;
    private needHash: boolean = false;

    constructor(ignores?: IIgnoreOption, needHash?: boolean, cb?: () => void) {
        this.ignores = ignores;
        this.callback = cb;
        this.needHash = needHash;
        this.fileNum = 0;
    }

    public cp(dirPath: string, toDirPath: string) {
        fs.stat(dirPath, (err, stats) => {
            if (err) {
                console.log(`这个目录${dirPath}状态不对呀`);
                return;
            }
            if (stats.isDirectory()) {
                if (isIgnoreDir(dirPath, this.ignores)) {
                    return;
                }
                if (!fs.existsSync(toDirPath)) {
                    createDirSync(toDirPath);
                }
                fs.readdir(dirPath, (err2, files) => {
                    for (const ifile of files) {
                        let ifurl = path.join(dirPath, ifile);
                        if (fs.statSync(ifurl).isFile()) {

                            // console.log(isIgnoreFile(ifurl, this.ignores), '要忽略ma');
                            if (isIgnoreFile(ifurl, this.ignores)) {

                                continue;
                            }
                            this.fileNum++;
                            // console.log('add: ', this.fileNum);
                        }
                        this.cp(ifurl, path.join(toDirPath, ifile));
                    }
                });
            } else {
                let f = fs.readFileSync(dirPath);
                let realFilePath = this.getRealFilePath(toDirPath, f);
                fs.writeFile(realFilePath, f, (err3) => {
                    if (err3) {
                        console.log(`拷贝文件出错${realFilePath}`);
                        return;
                    }
                    this.fileNum--;
                    // console.log('now: ', this.fileNum);

                    if (this.fileNum === 0) {
                        if (this.callback !== null) {
                            this.callback.call(null);
                        }
                    }
                });
            }
        });
    }

    private getRealFilePath(oldPath: string, fileData?: Buffer) {
        if (this.needHash) {
            return hashFile(oldPath, fileData);
        } else {
            return oldPath;
        }
    }
}

export function cpDir(dirPath: string, toDirPath: string, callback?: () => void,
    hash: boolean = false, ignores?: IIgnoreOption) {

    let cpdir = new CpDir(ignores, hash, callback);
    cpdir.cp(dirPath, toDirPath);
}

export function cpFile(filePath: string, toFilePath: string, callback?: () => void) {
    let dirPath = path.dirname(toFilePath);
    createDirSync(dirPath, () => {
        fs.copyFileSync(filePath, toFilePath);
        callback && callback();
    })
}

export async function removeDir(dir: string) {
    return new Promise(function (resolve, reject) {
        //先读文件夹
        fs.stat(dir, function (err, stat) {
            if(err){
                reject(err);
            }
            if (stat.isDirectory()) {
                fs.readdir(dir, function (err, files) {
                    files = files.map(file => path.join(dir, file)); // a/b  a/m
                    let promises = files.map(file => removeDir(file)); //这时候变成了promise
                    Promise.all(promises).then(function () {
                        fs.rmdir(dir, resolve);
                    })
                })
            } else {
                fs.unlink(dir, (err) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(dir);
                    }
                });
            }
        })

    })
}

class Dir {
    private fileNum = 0;
    private complete?: () => void;
    private eachFunc?: (fn: string) => void;
    private ignores: IIgnoreOption;

    constructor(ignores?: IIgnoreOption, eachFunc?: (fn: string) => void, complete?: () => void) {
        this.ignores = ignores;
        this.complete = complete;
        this.eachFunc = eachFunc;
        this.fileNum = 0;
    }

    public foreach(dirPath: string) {
        let s = fs.statSync(dirPath);
        if (s.isDirectory()) {
            if (isIgnoreDir(dirPath, this.ignores)) {
                return;
            }
            fs.readdir(dirPath, (err, files) => {
                for (const fn of files) {
                    let ifurl = path.join(dirPath, fn);
                    if (fs.statSync(ifurl).isFile()) {
                        if (isIgnoreFile(ifurl, this.ignores)) {
                            continue;
                        }
                        this.fileNum++;
                    }
                    this.foreach(ifurl);
                }
            });
        } else {
            this.fileNum--;
            if (this.eachFunc !== null) {
                this.eachFunc(dirPath);
            }
            if (this.fileNum === 0) {
                console.log('all files each over!');
                if (this.complete !== null) {
                    this.complete();
                }
            }
        }
    }

}

export function forEachByDir(dirPath: string, ignores?: IIgnoreOption,
    eachFunc?: (fn: string) => void, complete?: () => void) {

    let dir = new Dir(ignores, eachFunc, complete);
    dir.foreach(dirPath);
}
